/*
 * msgextract.c : extract strings from files and merge them with existing language file
 *
 * Written by T.Pierron, Mar 2008.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include "UtilityLib.h"
#include "msgextract.h"


ListHead strings = {0};
ListHead langs   = {0};
ListHead skip    = {0};
int      total   = 0;

int main(int nb, char * argv[])
{
	UtilityInit("msgextract");

	if (nb != 2)
	{
		fprintf(stderr, "usage: %s <conf>\n", *argv);
		return 1;
	}

	INIFile conf = ParseINI(argv[1]);

	if (conf)
	{
		STRPTR folder, prefix, suffix;
		STRPTR k, v;
		LangFile l;
		Bool unescape;

		folder = prefix = suffix = NULL;
		unescape = True;

		while (IterINI(conf, &k, &v))
		{
			if (v == NULL)
			{
				if (folder && prefix && suffix)
					ExtractMessages(folder, prefix, suffix, unescape);

				folder = prefix = suffix = NULL;
				unescape = True;
			}
			else switch (FindInList("Folder,Prefix,Suffix,Lang,Skip,Unescape", k, 0)) {
			case 0: folder = v; break;
			case 1: prefix = v; break;
			case 2: suffix = v; break;
			case 3: ScanDir(v, ParseLangs, NULL); break;
			case 4: LangAddLine(&skip, v, NULL, NULL, 0); break;
			case 5: unescape = strtol(v, NULL, 10);
			}
		}
		if (folder && prefix && suffix)
			ExtractMessages(folder, prefix, suffix, unescape);
		FreeINI(conf);

		fprintf(stderr, "%d files processed for message translation.\n", total);

		for (l = HEAD(langs); l; NEXT(l))
		{
			String s;
			Msg    m;

			for (m = HEAD(strings); m; m->flag = 0, NEXT(m));

			for (s = HEAD(l->file->strings); s; NEXT(s))
			{
				if (s->type) continue;

				for (m = HEAD(strings); m && strcmp(m->text, s->str); NEXT(m));

				if (m == NULL)
				{
					/* String does not exists anymore : comment it when rewriting file */
					s->type = 2;
				}
				else m->flag = 1; /* Mark message as OLD */
			}

			/* Now we have : old messages in "l->file->strings" marked (type == 2).
			 *               new messages in "strings".
			 *               current messages in l->file->strings (type == 0).
			 */
			LangRewrite(l);
		}
	}
	else fprintf(stderr, "Cannot open '%s': %s\n", argv[1], GetError());

	return 0;
}

static int LangComment(STRPTR * lang, int sz, int offset)
{
	STRPTR p = *lang + offset;
	Bool   f = True;
	int    m = (sz + 255) & ~255;
	STRPTR end, t;

	for (; p; f = False, p = end)
	{
		end = NextLine(p, False);
		if (f == False)
		{
			/* Check if this line is a continuation of previous */
			for (t = p; *t && *t != '\n' && *t != '\r' && isspace(*t); t ++);
			if (t == p || *t != '\"') return sz;
		}
		if (sz + 2 > m)
		{
			m = (sz + 257) & ~255;
			t = realloc(*lang, m);
			if (t == NULL) return sz;
			p = t + (p - *lang);
			end = t + (end - *lang);
			*lang = t;
		}
		memmove(p + 2, p, sz - (p - *lang));
		memcpy(p, "# ", 2);
		sz += 2; end += 2;
	}
	return sz;
}

static void LangRewrite(LangFile l)
{
	STRPTR old = CatPath(l->path, "../old", BaseName(l->path), NULL);
	FILE * out;
	String s;
	Msg    m;
	int    sz;
	int    nbadd = 0;
	int    nbdel = 0;
	int    nbrem = 0;

	/* Save the old one, just in case */
	CreatePath(old, True);
	FileCopy(l->path, old, True);
	free(old);

	/* Read the whole file at once, we'll comment obsolete message in memory and overwrite everything at once */
	out = fopen(l->path, "rb");
	sz  = FileSize(l->path) + 1;
	old = malloc((sz + 255) & ~255);

	fread(old, 1, sz, out); old[sz - 1] = 0;
	fclose(out);

	for (s = TAIL(l->file->strings); s; PREV(s))
	{
		if (s->origin > 0) continue; /* Quoted from an included file */
		if (s->type != 2) { nbrem ++; continue; }

		sz = LangComment(&old, sz, s->offset_trans);
		sz = LangComment(&old, sz, s->offset_str);
		nbdel ++;
	}

	out = fopen(l->path, "wb");
	if (out)
	{
		Bool warnednew = False;

		fwrite(old, 1, sz - 1, out);

		/* Put new messages after old ones */
		for (m = HEAD(strings); m; NEXT(m))
		{
			if (m->flag == 1) continue;
			if (! warnednew)
				fprintf(out, "\n# NEW MESSAGES\n"), warnednew = True;

			WriteString("msgid ", "      ", m->text, out);

			/* Empty translation */
			fprintf(out, "msgstr \"\"\n\n");
			nbadd ++;
		}
		fprintf(stderr, "%s updated: %d added, %d commented, %d untouched.\n", BaseName(l->path),
			nbadd, nbdel, nbrem);

		fclose(out);
	}
	else fprintf(stderr, "Cannot write '%s': %s\n", l->path, GetError());
}

static void WriteString(STRPTR prefix1, STRPTR prefix2, STRPTR str, FILE * out)
{
	fprintf(out, "%s\"", prefix1);

	while (*str)
	{
		switch (*str) {
		case '\t': fprintf(out, "\\t"); break;
		case '\n': fprintf(out, "\\n"); break;
		case '\r': fprintf(out, "\\r"); break;
		case '\v': fprintf(out, "\\v"); break;
		case '\\': fprintf(out, "\\\\"); break;
		case '\"': fprintf(out, "\\\""); break;
		default:
			if (* (unsigned char *) str < 32) fprintf(out, "\\x%02x", *str);
			else fputc(*str, out);
		}
		str ++;
	}
	fprintf(out, "\"\n");
}


/* Scan file for string to be translated */
static void ExtractMessages(STRPTR folder, STRPTR prefix, STRPTR suffix, Bool unescape)
{
	STRPTR pat = BaseName(folder);
	APTR args[3];

	if (pat > folder) pat[-1] = 0;
	else folder = ".";

	args[0] = prefix;
	args[1] = suffix;
	args[2] = pat;
	args[3] = (APTR) unescape;
	ScanDir(folder, ParseFile, args);
}

/* Parse source file for message to translate */
static int ParseFile(STRPTR dir, STRPTR file, APTR ud)
{
	if (FnMatch(PARAM(ud, 2), file, FNM_CASEFOLD))
	{
		STRPTR path = CatPath(dir, file, NULL);
		File   in   = FILE_Open(path, FILE_IO_STDIO);

		fprintf(stderr, "parsing %s...\n", path);

		if (in)
		{
			STRPTR prefix = PARAM(ud, 0);
			STRPTR suffix = PARAM(ud, 1);
			STRPTR token, end;
			Msg    msg, m;
			String s;

			while (FILE_ReadLine(in))
			{
				end = in->buffer;
				while ((token = strstr(end, prefix)))
				{
					token += strlen(prefix);
					end = strstr(token, suffix);
					if (end == NULL) break;
					msg = malloc(sizeof *msg + end - token + 1);
					CopyString(msg->text, token, end - token + 1);
					if (PARAM(ud, 3)) UnescapeAntiSlash(msg->text);
					end += strlen(suffix);

					/* Check if string has already been added */
					for (m = HEAD(strings); m && strcmp(m->text, msg->text); NEXT(m));

					/* Check if string match reject list */
					for (s = HEAD(skip); s && ! FnMatch(s->str, msg->text, 0); NEXT(s));

					if (s == NULL && m == NULL) ListAddTail(&strings, &msg->node);
					else free(msg);
				}
			}
			FILE_Close(in);
		}
		free(path);
		total ++;
	}
	return 0;
}

/* Get messages already translated */
static int ParseLangs(STRPTR dir, STRPTR file, APTR ud)
{
	STRPTR path = CatPath(dir, file, NULL);

	if (! IsDir(path))
	{
		Lang lang = ParseLang(file, path);

		if (lang)
		{
			LangFile lf = malloc(sizeof *lf);

			ListAddTail(&langs, &lf->node);
			lf->path = path;
			lf->file = lang;
			return 0;
		}
	}
	free(path);
	return 0;
}

static void FreeLang(Lang lang)
{
	LangStack * ls;
	ListNode *  node;
	int i;

	for (ls = lang->files + DIM(lang->files) - 1, i = lang->count; i < DIM(lang->files); free(ls->buffer), i ++, ls --);
	while ((node = ListRemHead(&lang->strings))) free(node);
	free(lang);
}

static STRPTR LangStackFile(Lang lang, STRPTR path, STRPTR eol)
{
	FILE * in;
	STRPTR rel      = lang->stack > 0 ? lang->files[lang->stack-1].file : NULL;
	ULONG  length   = StrLenM(path, rel, NULL) + 2;
	STRPTR fullpath = alloca(length);
	STRPTR buffer   = NULL;

	CopyString(fullpath, rel, length);
	ParentDir(fullpath);
	AddPart(fullpath, path, length);

	in = fopen(fullpath, "rb");

	if (in)
	{
		LangStack * ls = lang->files + lang->stack;

		length = FileSize(fullpath);
		buffer = malloc(length + strlen(fullpath) + 2);
		buffer[length] = 0;
		fread(buffer, 1, length, in);
		fclose(in);

		ls->file = strcpy(buffer + length + 1, fullpath);
		ls->buffer = buffer;
		ls->eol = eol;
		lang->stack ++;
	}
	else fprintf(stderr, "Fail to open '%s': %s\n", fullpath, GetError());
	return buffer;
}

static STRPTR LangPopFile(Lang lang, STRPTR * start)
{
	if (lang->stack > 0)
	{
		LangStack * ls = lang->files + (-- lang->stack);
		STRPTR eol = ls->eol;

		lang->files[-- lang->count] = *ls;
		if (lang->stack > 0)
		{
			ls --;
			*start = ls->buffer;
			return eol;
		}
	}
	*start = NULL;
	return NULL;
}


static Lang ParseLang(STRPTR name, STRPTR path)
{
	STRPTR msgid  = NULL;
	STRPTR msgstr = NULL;
	STRPTR line, eol, buffer;
	Lang   lang;
	int    offset[2];

	lang = calloc(sizeof *lang, 1);
	lang->count = DIM(lang->files);
	buffer = LangStackFile(lang, path, NULL);
	eol = NULL;

	CopyString(lang->name, name, sizeof lang->name);

	while (buffer)
	{
		for (line = eol ? eol : buffer; line; line = eol)
		{
			eol = NextLine(line, True);

			if (*line == '#')
			{
				if (strncasecmp(line + 1, "include ", 8) == 0)
				{
					buffer = LangStackFile(lang, line + 9, eol);

					if (buffer) eol = buffer;
				}
				else if (strncasecmp(line + 1, "name ", 5) == 0 && lang->stack == 1)
				{
					CopyString(lang->name, line + 6, sizeof lang->name);
				}
				else if (strncasecmp(line + 1, "msgextract skip", 15) == 0)
				{
					FreeLang(lang);
					return NULL;
				}
				continue;
			}

			if (strncasecmp(line, "msgid ", 6) == 0)
			{
				if (msgstr && IsDef(msgid))
				{
					/* print "adding \"$msgid\" => \"$msgstr\"\n"; */
					LangAddLine(&lang->strings, msgid, msgstr, offset, lang->stack > 1);
				}
				msgstr = msgid = NULL;
				msgid  = LangExtractStr(msgid, line);
				offset[0] = line - buffer;
			}
			else if (strncasecmp(line, "msgstr ", 7) == 0)
			{
				msgstr = LangExtractStr(msgstr, line);
				offset[1] = line - buffer;
			}
			else if (msgstr)
			{
				msgstr = LangExtractStr(msgstr, line);
			}
			else if (msgid)
			{
				/* continuation of msg ID */
				msgid = LangExtractStr(msgid, line);
			}
		}
		if (msgstr && IsDef(msgid))
		{
			LangAddLine(&lang->strings, msgid, msgstr, offset, lang->stack > 1);
		}
		msgstr = msgid = NULL;
		eol = LangPopFile(lang, &buffer);
	}

	return lang;
}

static void LangAddLine(ListHead * head, STRPTR msgid, STRPTR msgstr, int * offset, int origin)
{
	String s = malloc(sizeof *s);

	ListAddTail(head, &s->node);
	s->str = msgid;
	s->trans = msgstr;
	s->type = (msgstr == NULL);
	s->origin = origin;
	if (offset)
	{
		s->offset_str = offset[0];
		s->offset_trans = offset[1];
	}
	else s->offset_str = s->offset_trans = 0;
}

static STRPTR LangExtractStr(STRPTR old, STRPTR buffer)
{
	STRPTR quote = strchr(buffer, '\"');
	STRPTR end;

	if (quote == NULL) return old;

	for (end = ++ quote; *end && *end != '\"'; end ++)
		if (*end == '\\' && end[1]) end ++;
	*end = 0;
	UnescapeAntiSlash(quote);
	if (old)
	{
		memmove(strchr(old, 0), quote, strlen(quote) + 1);
		return old;
	}
	return quote;
}
